package featuresProcessing;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import utils.Species;
/**
 * 
 * Implementation of the Detector interface, using the Suzuky's shape matching algorithm.
 * Before extracting the descriptors, the leaf is preprocessed with a blurring, a Canny edge detection algorithm and
 * finally a morphological closing in ordet to close eventually open contours. Then all the contours are extracted and
 * the one with the largest area (hopefully related to the leaf edge) is selected.
 * The descriptors consist of a list of points which define, up to a certain approximation,
 * the edge of the leaf.
 * The matching is performed on each leaf and the ranking is returned in increasing order of distance.
 *
 */
public class ShapeDetector implements Detector {


	private HashMap<Integer,Mat> shapeDB;
	
	
	public ShapeDetector() {
		shapeDB = new HashMap<Integer,Mat>();
	}
	
	
	
	public MatOfPoint getDescriptors(Mat image) {
		
		Mat edges = new Mat();
		Imgproc.GaussianBlur(image, edges, new Size(9,9),1.5);
		Imgproc.Canny(image, edges, 80, 90);
//		Imgproc.threshold(edges, edges, 30, 255, Imgproc.THRESH_BINARY);
		Imgproc.morphologyEx(edges, edges, Imgproc.MORPH_CLOSE, Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(6,6)));
//		Imgproc.adaptiveThreshold(image, edges, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY_INV, 7, 5);
//		ImageUtils.showImage(edges);
		
		List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
		Mat hierarchy = new Mat();
		Imgproc.findContours(edges, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
		MatOfPoint biggestContour = null;
		double biggestContourArea = 0;
		for (int i = 0; i < contours.size(); i++) {
			
			
			double area = Imgproc.contourArea(contours.get(i));
			if (biggestContourArea<area) {
				biggestContourArea=area;
				biggestContour=contours.get(i);
			}
		}
		
//		Mat ci = new Mat(edges.size(), 5);
//		ArrayList<MatOfPoint> list = new ArrayList<MatOfPoint>();
//		list.add(biggestContour);
//		Imgproc.drawContours(ci, list, -1, new Scalar(255,10,10));		
//		ImageUtils.showImage(ci);
		
//		BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
//		try {
//			r.readLine();
//		} catch (IOException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
		
		return biggestContour;
	}

	@Override
	public void addTrainingDescriptors(Collection<Species> setOfImages) {
		Iterator<Species> it = setOfImages.iterator();
		while (it.hasNext()) {
			Species s = it.next();
			shapeDB.put(s.getId(), s.getShapeDescriptors());
		}
	}

	@Override
	public HashMap<Integer, Float> classify(Mat descriptors) throws EmptyClassifierException {
		if (shapeDB.size() == 0)
			throw new EmptyClassifierException("You must fill the training set before classifying.");
		
		HashMap<Integer, Float> distances = new HashMap<Integer, Float>();
		Iterator<Integer> it = shapeDB.keySet().iterator();
		while (it.hasNext()) {
			int key = it.next();
			float distance = (float)(Imgproc.matchShapes(descriptors, shapeDB.get(key), Imgproc.CV_CONTOURS_MATCH_I3, 0));
			if (distance < 0.4)
				distances.put(key, distance/0.4f);
		}
		return distances;
	}	
	
	

}
